import copy

from cnrp_experiment.exp_chain import TestBlockChain
from cnrp_experiment.exp_tools import *
from cnrp_experiment.exp_block import *


# 基本型 node
class TestNode(object):

    def __init__(self, node_id: int):
        # node id <int>
        self.node_id = node_id
        # 当前的执行队列 可供request的队列集合 element: tx_id
        self.execute_queue = {'request': [], 'share': [], 'evaluate': []}
        # 发布的 tx 计数
        self.tx_count = 0

        # 忍耐度 [0, 1] 决定 request 和 share 队列中信誉值的下限
        self.degree_endurance = 0.5
        # 活跃值(决定被抽到发言的概率)
        self.degree_active = 5
        # TODO 发布的数据质量 {0.7: 10, 0.2: 1}
        self.degree_data_quality = {0.3: 0, 0.6: 10}
        # 评价打分偏好 {0.7: 10, 0.2: 1}
        self.degree_evaluate = {0.3: 0, 0.6: 10}
        # 评价打分偏离值（比真实的数据质量的偏移值）
        self.degree_evaluate_offset = 0
        # 类型偏好
        self.degree_act_prefer = {'upload': 4, 'request': 6, 'share': 8, 'evaluate': 10}

    # 向 execute_queue 添加 tx_id
    def execute_queue_append(self, queue_type: str, tx_id: int):
        self.execute_queue[queue_type].append(tx_id)

    # 向 execute_queue 删除 tx_id
    def execute_queue_remove(self, queue_type: str, tx_id: int):
        self.execute_queue[queue_type].remove(tx_id)

    # 生成 tx 的类型 tx_type
    def produce_tx_type(self, tx_id: int):
        # 策略: 优先完成当前 business
        tx_type = ''
        for t in ['evaluate', 'share', 'request']:
            if self.execute_queue[t]:
                tx_type = t
        if tx_type == '':
            tx_type = 'upload'
        return tx_type

    # 生成 tx 的前向 prev_tx_id
    def produce_tx_prev_id(self, tx_id: int, tx_type: str):
        # 策略: 对于upload类型 prev_tx_id 值设定为-1 否则为应当的 prev_tx_id 值
        prev_tx_id = -1
        if tx_type != 'upload':
            # @TODO
            prev_tx_id = self.execute_queue[tx_type][-1]
            del self.execute_queue[tx_type][-1]
        return prev_tx_id

    # 生成 tx 的打分 仅限 evaluate 类型
    def produce_tx_data_score(self, tx_id: int, tx_type: str, prev_tx_id):
        # 策略: 对于 evaluate 类型 score = data_quality + evaluate_offset
        data_score = 0.7
        if tx_type == 'evaluate':
            share_tx = TestBlockChain.get_tx_by_id(prev_tx_id)
            request_tx = TestBlockChain.get_tx_by_id(share_tx.prev_tx_id)
            upload_tx = TestBlockChain.get_tx_by_id(request_tx.prev_tx_id)
            # TODO
            # data_score = upload_tx.data_quality + self.degree_evaluate_offset
            data_score = (upload_tx.data_quality + self.degree_evaluate_offset) % 1.0
        return data_score

    # 生成 tx 的数据质量 仅限 upload 类型
    def produce_tx_data_quality(self, tx_id: int):
        # 策略: 根据对象本身的数据质量 dict 随机抽取
        data_quality = luck_draw(self.degree_data_quality)
        return data_quality

    # 附加操作
    def extra_action(self, tx_id: int, tx_type: str, prev_tx_id: int, data_score: int, data_quality: int):
        return

    # 获得一个 block: 基准 block
    def produce_tx(self, tx_id: int):

        # 设置项
        tx_type = self.produce_tx_type(tx_id)
        prev_tx_id = self.produce_tx_prev_id(tx_id, tx_type)
        data_score = self.produce_tx_data_score(tx_id, tx_type, prev_tx_id)
        data_quality = self.produce_tx_data_quality(tx_id)

        # 附加操作
        self.extra_action(tx_id, tx_type, prev_tx_id, data_score, data_quality)

        # 生成 block
        tx = TestTransaction(tx_id, tx_type, self.node_id)
        tx.prev_tx_id = prev_tx_id
        tx.data_score = data_score
        tx.data_quality = data_quality

        self.tx_count += 1
        return tx


# 基本型 node 修改 tx_type 策略
class TestNodeBase(TestNode):

    def produce_tx_type(self, tx_id: int):
        # tx_type 策略: 随机抽取可行的类型
        tx_type_option = {}
        for t in ['evaluate', 'share', 'request']:
            if self.execute_queue[t]:
                tx_type_option[t] = self.degree_act_prefer[t]
        tx_type_option['upload'] = self.degree_act_prefer['upload']
        tx_type = luck_draw(tx_type_option)
        return tx_type


# 由好变坏 node 继承自 TestNodeBase
# 在某个 tx_count 位置发生行为变化 上传低质量数据
class TestNodeGoodToBad(TestNode):

    def produce_tx_data_quality(self, tx_id: int):
        if self.tx_count > 5:
            self.degree_data_quality = {0.7: 0, 0.2: 10}
        # 策略: 根据对象本身的数据质量 dict 随机抽取
        data_quality = luck_draw(self.degree_data_quality)
        return data_quality


# 不上传数据的 node 继承自 TestNodeBase
class TestNodeNoUpload(TestNodeBase):

    def produce_tx_type(self, tx_id: int):
        # tx_type 策略: 优先完成当前 business
        tx_type = 'request'
        for t in ['evaluate', 'request']:
            if self.execute_queue[t]:
                tx_type = t
        if not self.execute_queue[tx_type]:
            return False
        return tx_type

    # 获得一个 block: 基准 block
    def produce_tx(self, tx_id: int):

        # 设置项
        tx_type = self.produce_tx_type(tx_id)
        if not tx_type:
            return False
        prev_tx_id = self.produce_tx_prev_id(tx_id, tx_type)
        data_score = self.produce_tx_data_score(tx_id, tx_type, prev_tx_id)
        data_quality = self.produce_tx_data_quality(tx_id)

        # 生成 block
        tx = TestTransaction(tx_id, tx_type, self.node_id)
        tx.prev_tx_id = prev_tx_id
        tx.data_score = data_score
        tx.data_quality = data_quality

        self.tx_count += 1
        return tx


# 从不上传 转变为 没有动作
class TestNodeNoUploadToQuiet(TestNodeNoUpload):

    # 获得一个 block: 基准 block
    def produce_tx(self, tx_id: int):
        if self.tx_count > 500:
        # if tx_id > 1000:
            return False
        # 设置项
        tx_type = self.produce_tx_type(tx_id)
        if not tx_type:
            return False
        prev_tx_id = self.produce_tx_prev_id(tx_id, tx_type)
        data_score = self.produce_tx_data_score(tx_id, tx_type, prev_tx_id)
        data_quality = self.produce_tx_data_quality(tx_id)

        # 生成 block
        tx = TestTransaction(tx_id, tx_type, self.node_id)
        tx.prev_tx_id = prev_tx_id
        tx.data_score = data_score
        tx.data_quality = data_quality

        self.tx_count += 1
        return tx


# 恶意节点 乱打分+发布低质量数据
# Bad Evaluate: high quality data -> low evaluate
class TestNodeBadEvaluateAndLowQualityData(TestNodeBase):

    def __init__(self, node_id: int):
        super().__init__(node_id)
        self.degree_data_quality = {0.2: 10, 0.7: 0}
        self.degree_evaluate_offset = 0.5


# 待完善 2022.2.23
# 具有审核机制的一般节点
# 审核机制: request 和 share 行为取决于对方节点的信誉值
class TestNodeAdvanced(TestNode):

    # 根据 cnrp 判定是否应响应给定 node 的请求 主要是 request 和 share 请求
    def _judge_should_response(self, cnrp: list, node_id: int):
        # Algorithm 1: 数差法
        # length = len(cnrp)
        # diff_list = []
        # for i in range(1, length):
        #     diff_list.append(cnrp[i][1] - cnrp[i-1][1])
        # diff_mean = sum(diff_list) / (length - 1)
        # gap = 0
        # for j in range(length - 1):
        #     if diff_list[j] > diff_mean:
        #         gap = j
        #         break
        # blacklist = [_[0] for _ in cnrp[:gap+1]]
        # if node_id in blacklist:
        #     return False
        # else:
        #     return True

        # Algorithm 2: 利用忍耐度计算
        rp = [_[1] for _ in cnrp]
        mean = sum(rp) / len(rp)

        node_rp = 0
        for tup in cnrp:
            if tup[1] > mean:
                break
            if tup[0] == node_id:
                node_rp = tup[1]



        pass

    def execute_queue_append(self, queue_type: str, tx_id: int):
        # 升序 list
        cnrp = TestBlockChain.get_cur_cnrp('ascend')

        if queue_type == 'request':
            pass
            # if TestBlockChain.get_tx_by_id(tx_id).source_node_id
        elif queue_type == 'share':
            pass
        else:
            pass
        self.execute_queue[queue_type].append(tx_id)

